import java.io.EOFException;
import java.io.FileNotFoundException;
import java.nio.charset.CharacterCodingException;
import java.util.HashMap;

public class MyXmlParser
{
	public static void main(String[] arguments)
	{
		if (arguments.length == 1)
		{
			new MyXmlParser(arguments[0]);
		}
		else
		{
			System.out.println(
					"MyXmlParser\n\n" +
					"Example:\n" +
					"java MyXmlParser note.xml\n\n" +
					"Usage:\n" +
					"java MyXmlParser file\n\n" +
					"Arguments:\n" +
					"file       the file to be checked\n" +
					"           Example: note.xml\n");
		}
	}
	/*
	[X] All XML documents must have one root element, 
	[X] All XML elements must be properly nested,
	[X] All not empty XML elements must have a closing tag,
	[X] Empty Elements,
	[] Comments,   
	[] <![CDATA[…]]>
	[X] <?xml … ?>
	[] <!DOCTYPE … >
	*/
	public MyXmlParser(String filename)
	{
		try
		{
			HashMap<Integer, String> stack = new HashMap<Integer, String>();
			Integer stackPointer = new Integer(0);
			
			MyPushbackReader stream = new MyPushbackReader(filename);
			
			String lastToken = "";
			
			// First validate the first token
			String token = stream.nextToken();
			
			if (token.startsWith("<?xml") == false || token.endsWith("?>") == false)
			{
				System.out.println(token);
				throw new Exception("First token failed");
			}
			
			try
			{
				while (true)
				{
					token = stream.nextToken();
					
					if (token.startsWith("<") && token.endsWith(">"))
					{
						// </kees>
						if (token.startsWith("</"))
						{
							// Close tag
							token = stripToken(token);
							
							// </kees>
							if (stack.get(stackPointer - 1).equals(token))
							{
								// <kees></kees>
								if (lastToken.isEmpty())
								{
									throw new Exception("Elements must not be empty. You should use the <tag /> notation instead.");
								}
								
								stackPointer--;
							}
							// <b><i>text</b></i> (not well nested).
							else
							{
								System.out.println(stack.get(stackPointer - 1)+" vs "+token);
								throw new Exception("Stack error. A tag closed to early: " + token);
							}
						}
						// Comment, Doctype or CDATA.
						else if (token.startsWith("<!"))
						{
							if (!(token.startsWith("<!--") && token.endsWith("-->") ||
								  token.startsWith("<![CDATA[") && token.endsWith("]]>") ||
								  token.startsWith("<!DOCTYPE")))
							{
								throw new Exception("Invalid tag: " + token);
							}
						}
						else
						{
							if (stackPointer == 0 && stack.get(0) != null)
							{
								throw new Exception("The XML document must only have one root element.");
							}
							
							// <kees />
							if (token.endsWith("/>"))
							{
								continue;
							}
							// <kees>
							else
							{
								// Open tag
								token = stripToken(token);
								
								stack.put(stackPointer++, token);
							}
						}
					}
					else
					{
						// Plain data
						lastToken = token;
						
						if
						(
								token.indexOf('<') != -1
								|| token.indexOf('>') != -1
						//		|| token.indexOf('‘') != -1
						//		|| token.indexOf('"') != -1
						)
						{
							throw new Exception("The plain data contains invlaid chars.");
						}
					}
				}
			}
			catch (EOFException e)
			{
				if (stackPointer != 0)
				{
					
					throw new Exception("Pointer is not empty on end of file.");
				}
				
				System.out.println("The file is well-formed.");
			}
		}
		catch (CharacterCodingException e)
		{
			System.out.println("The file encoding is not accepted. Only UTF-8 encoding is supported.");
		}
		catch (FileNotFoundException e)
		{
			System.out.println("The file could not be found.");
		}
		catch (Exception e)
		{
			System.out.println("The file is NOT well-formed.");
			System.out.flush();
			
			e.printStackTrace();
		}
	}
	
	private String stripToken(String token)
	{
		String[] pieces = token.substring(1, token.length() - 1).trim().split("\\s");
		
		String stripped = pieces[0];
		
		if (stripped.charAt(0) == '/')
		{
			stripped = stripped.substring(1);
		}
		
		return stripped;
	}
}
